import Designer from '@/index';
import Node from '../node';
import Stage from '../stage';
import Text from '../text';
import { getImage } from '../image';
import { draw, getType, isDef, isNumber, isPointInsideCircle, log, midpoint, vectorRotation } from '@/utils';
import { defaultStyle, computedStyle, computedNameStyle } from './constant';

/**
 * 逻辑块类
 */
export default class Logic extends Node {
  className = 'Logic'; // 类名
  constructor(config = {}) {
    super(config);
    // 是否展示逻辑块 name
    this.nameShow = config.nameShow ?? true;
    // 逻辑块 name 样式配置
    this.nameStyle = config.nameStyle ?? null;
    // 描边颜色
    this.strokeStyle = config.strokeStyle ?? defaultStyle.strokeStyle;
    // 描边线宽
    this.strokeWidth = config.strokeWidth ?? 2;
    // 描述虚线模式的线和间隙的交替长度
    this.strokeDash = config.strokeDash ?? defaultStyle.strokeDash;
    // 描边圆角半径
    this.strokeRadius = config.strokeRadius ?? 8;
    // 是否填充
    this.fill = config.fill ?? true;
    // 填充颜色
    this.fillStyle = config.fillStyle ?? defaultStyle.fillStyle;
    // 内边框描边颜色
    this.innerStrokeStyle = config.innerStrokeStyle ?? defaultStyle.innerStrokeStyle;
    // 空锚点占位文本
    this.emptyText = config.emptyText ?? '';
    // 图标url
    this.icon = config.icon ?? undefined;
    // 设置图标
    this.setIcon(this.icon);
  }
  // 默认锚点集（默认四个边中心点）
  get defaultAnchors() {
    return [];
  }
  // 私有锚点集
  get _anchors() {
    if (!this.hasAnchors) return [];
    const { anchors, width, gridScale } = this;
    const { gridCoord, hoveringAnchor, allEffectEdges } = this.stage;
    const anchorLength = 20; // 引脚线长度
    const textOffset = 10; // 引脚名文本偏移量
    return anchors.reduce((arr, anchor, index) => {
      const { visible, id, side, offset = 0 } = anchor;
      // 不同边获取不同的引脚点
      const points = {
        left: {
          o: { x: 0, y: offset },
          d: { x: 0 - anchorLength, y: offset },
          t: { x: 0 + textOffset, y: offset },
        },
        right: {
          o: { x: width, y: offset },
          d: { x: width + anchorLength, y: offset },
          t: { x: width - textOffset, y: offset },
        },
      }[side];
      const _anchor = {
        ...anchor,
        visible: visible !== false && isDef(id) && points,
        node: this,
        index,
        ...points,
        hovering: hoveringAnchor?.node === this && hoveringAnchor?.id === id,
        effecting: allEffectEdges.some(({ source, sourceAnchor, target, targetAnchor }) => {
          return (source === this.id && sourceAnchor === id) || (target === this.id && targetAnchor === id);
        }),
      };
      arr.push(_anchor);
      if (!_anchor.visible) return arr;
      // 网格坐标
      const gridO = this.getGridCoord(_anchor.o);
      const gridD = this.getGridCoord(_anchor.d);
      // 连线点
      _anchor.p = gridD;
      // hover 判定
      _anchor.isPointIn = isPointInsideCircle(gridCoord, midpoint(gridO, gridD), anchorLength * gridScale);
      // 锚点射出角度（用于平滑节点连线的锚点出入）
      _anchor.v = vectorRotation(gridO, gridD);
      return arr;
    }, []);
  }
  // 绘制锚点
  drawAnchors(ctx) {
    // 引脚线宽
    const pinWidth = 2;
    const { _anchors } = this;
    const { selectedNodes } = this.stage;
    const selected = selectedNodes.includes(this);
    _anchors.forEach(({ visible, side, name, logicType, o, d, t, hovering, effecting }) => {
      if (!visible) return;
      const style = computedStyle(this, { selected, anchorHovering: hovering, anchorEffecting: effecting });
      // 绘制引脚
      draw.line(ctx, { o, d, strokeStyle: style.pinColor, lineWidth: pinWidth, lineCap: 'round' });
      // 绘制引脚名
      if (name) draw.text(ctx, { text: name, x: t.x, y: t.y, color: style.nameColor, align: side });
      // 绘制逻辑类型标记
      const fillColor = { event: style.pinColor, listener: 'white' }[logicType];
      if (fillColor) {
        const radius = 5;
        draw.arc(ctx, { ...o, radius, lineWidth: pinWidth, color: fillColor });
        draw.arc(ctx, { ...o, radius, lineWidth: pinWidth, color: style.pinColor, isFill: false });
      }
    });
  }
  // 判断点是否在节点中
  isPointIn() {
    const { width, height, strokeWidth, strokeRadius, offsetCoord } = this;
    const { x, y } = offsetCoord;
    const ctx = Stage.canvas.getContext();
    const path = new Path2D();
    path.roundRect(0, 0, width, height, strokeRadius);
    ctx.lineWidth = strokeWidth;
    const isPointIn = ctx.isPointInPath(path, x, y) || ctx.isPointInStroke(path, x, y);
    ctx.lineWidth = 1;
    return isPointIn;
  }
  // 获取逻辑块 name 绘制信息
  getNameDrawInfo() {
    const { name, width, stage } = this;
    const calc = (num) => this.calc(num, stage);
    const offset = 5 * stage.scale; // 距离顶部偏移量
    const padding = 6; // 左右内边距
    const rectHeight = 28; // 文本容器高度
    const textMaxWidth = width - 2 * calc(padding); // 文本展示最大宽度
    const textWidth = Designer.Text.measureMaxWidth({ text: name }); // 文本测量最大宽度
    const rectMaxWidth = calc(textWidth + 2 * padding); // 文本容器撑开宽度
    const rectWidth = rectMaxWidth > width ? width : rectMaxWidth; // 文本容器宽度
    // 文本容器绘制信息
    const rectDrawInfo = {
      x: 0,
      y: -calc(offset + rectHeight),
      width: rectWidth,
      height: calc(rectHeight),
      radius: calc(4),
      lineWidth: calc(1),
    };
    // 文本绘制信息
    const textDrawInfo = {
      x: calc(padding),
      y: -calc(offset + rectHeight / 2),
      fontSize: calc(12),
      maxWidth: textMaxWidth,
    };
    return { rectDrawInfo, textDrawInfo };
  }
  // 绘制逻辑块 name
  drawName(ctx) {
    const { name } = this;
    // 获取最终计算后的逻辑块 name 样式配置
    const style = computedNameStyle(this);
    // 获取逻辑块 name 绘制信息
    const { rectDrawInfo, textDrawInfo } = this.getNameDrawInfo();
    // 绘制填充矩形
    draw.roundRect(ctx, { ...rectDrawInfo, color: style.background });
    // 绘制描边矩形
    draw.roundRect(ctx, { ...rectDrawInfo, color: style.border, isFill: false });
    // 绘制文本
    draw.text(ctx, { ...textDrawInfo, text: name, color: style.color });
  }
  // 绘制逻辑块 内容
  drawContent(ctx, style) {
    const { width, height, emptyAnchors, emptyText, image } = this;
    const center = { x: width / 2, y: height / 2 };
    // 空锚点状态 且 有空锚点占位文本 绘制占位文本
    if (emptyAnchors && emptyText) {
      const lineHeight = 16;
      const padding = 10;
      const textMaxWidth = width - 2 * padding; // 文本展示最大宽度
      const rows = Text.textRows({ text: emptyText }, textMaxWidth); // 获取文本行
      const offsetY = center.y - ((rows.length - 1) * lineHeight) / 2;
      // 逐行绘制文本
      rows.forEach((row, i) => {
        const finalY = i * lineHeight + offsetY;
        draw.text(ctx, { text: row, x: center.x, y: finalY, align: 'center', color: style.nameColor });
      });
    }
    // 绘制图标
    else if (image) {
      const size = 32;
      const transformConfig = { translateX: -size / 2, translateY: -size / 2 };
      try {
        draw.image(ctx, { image, ...center, width: size, height: size, transformConfig });
      } catch (error) {
        log.error(this, '图标渲染出错！');
        draw.text(ctx, { ...center, text: '图标加载失败', color: '#666', align: 'center' });
      }
    }
  }
  // 绘制主要内容
  drawMain(ctx, isExport) {
    const { nameShow, width, height, strokeWidth, strokeRadius, fill } = this;
    const { selectedNodes, hoveringNode } = this.stage;
    const selected = selectedNodes.includes(this);
    const hovering = hoveringNode === this;
    const style = computedStyle(this, { selected, nodeHovering: hovering });
    // 绘制逻辑块 name
    if (nameShow && !isExport) this.drawName(ctx);
    // 绘制外边框
    ctx.beginPath();
    ctx.strokeStyle = style.strokeStyle;
    ctx.lineWidth = strokeWidth;
    ctx.setLineDash(style.strokeDash);
    ctx.roundRect(0, 0, width, height, strokeRadius);
    // 背景填充
    if (fill) {
      ctx.fillStyle = style.fillStyle;
      ctx.fill();
    }
    ctx.stroke();
    // 绘制内边框
    const innerRadius = strokeRadius - strokeWidth < 0 ? 0 : strokeRadius - strokeWidth;
    ctx.beginPath();
    ctx.strokeStyle = style.innerStrokeStyle;
    ctx.setLineDash([]);
    ctx.roundRect(strokeWidth, strokeWidth, width - strokeWidth * 2, height - strokeWidth * 2, innerRadius);
    ctx.stroke();
    // 绘制逻辑块 内容
    this.drawContent(ctx, style);
  }
  /**
   * Methods
   */
  /**
   * 绘制轮廓
   */
  drawOutline(ctx, config) {
    // 非辅助线节点轮廓 不绘制逻辑块节点轮廓
    if (!config?.guideOutline) return;
    const { parent, stage } = this;
    if (!parent || !stage) return;
    const { scale: stageScale } = stage;
    const { edges } = this.gridRect;
    draw.outline(ctx, edges, {
      lineWidth: Designer.outlineLineWidth / stageScale, // 线宽
      ...config,
    });
  }
  /**
   * 设置图标
   * @param {string} icon 图标url
   */
  setIcon(icon) {
    const draw = () => this.visible && this.stage?.draw();
    // 逻辑块图标
    this.image = getType(icon) === 'String' ? getImage(icon) : null;
    if (this.icon !== icon) this.icon = icon;
    if (!this.image) return;
    // 监听加载完毕
    this.image.onload = () => draw();
    // 图片加载出错
    this.image.onerror = () => draw();
  }
  /**
   * Static Methods
   */
  /**
   * 测量逻辑块宽度
   */
  static measureWidth({ anchors = [] }, { diffSide = true, hasIcon = true } = {}) {
    // 记录最大宽度
    const maxWidthMap = { left: 0, right: 0 };
    anchors.forEach(({ visible, id, side, name }) => {
      // 是否绘制
      const isDraw = visible !== false && isDef(id) && isNumber(maxWidthMap[side]);
      if (!isDraw) return;
      // 测量锚点文本最大宽度
      const textWidth = Designer.Text.measureMaxWidth({ text: name });
      // 更新最大宽度
      if (maxWidthMap[side] < textWidth) maxWidthMap[side] = textWidth;
    }, 0);
    // 锚点名宽度合计
    let anchorsNameTotal = Math.max(...Object.values(maxWidthMap)) * 2;
    // 区分不同边的宽度
    if (diffSide) anchorsNameTotal = maxWidthMap.left + maxWidthMap.right;
    const textOffset = 10; // 引脚名文本偏移量
    const innerPadding = 20; // 内边距
    let finalWidth = anchorsNameTotal + textOffset * 2 + innerPadding;
    // 未带图标
    if (!hasIcon) return finalWidth;
    const iconWidth = 32; // 图标宽度
    // 带有图标
    return finalWidth + iconWidth;
  }
}
